
#include "../iface/APRInterface.h"
#include <apr_strings.h>
#include "../aprtools/APRFile.h"
#include "../net/Broker4.h"
#include "BaseConvert.h"
#include "StringUtils.h"
#include "StreamToDirectory.h"

#define min_v(a, b) (((a) < (b)) ? (a) : (b))

/** Constructor for the directory to be reconstructed from the stream.
 * 
 * @param directorypath
 * The path to the directory into which the stream is to be reconstituted.
 * 
 * @param imp
 * The memory pool from which to derive the memory pool for this object. */
StreamToDirectory::StreamToDirectory(const char *directorypath, apr_pool_t *imp)
{
   apr_status_t status;
   status = apr_pool_create(&mp, imp);
   CheckAPRError(status);
   directory_name = (char *)strdup(directorypath);
   nxtfilename = NULL;
   currflag = 0;
   currbytesthrough = 0;
}

/** Destructor for the directory reconstruction process. */
StreamToDirectory::~StreamToDirectory()
{
   if (nxtfilename) free(nxtfilename);
   nxtfilename = NULL;
   if (directory_name) free(directory_name);
   directory_name = NULL;
}

/** Reads an arithmetically stored integer from a set of 8 bytes.
 * 
 * @param bytes
 * The bytes into which the integer is stored.
 * 
 * @return
 * Returns the 64-bit integer which was stored in the stream. */
int64_t StreamToDirectory::readLogical64Bit(unsigned char *bytes)
{
   int64_t rd = 0;
   for (int i = 8; i--; )
   {
      rd += bytes[i];
      if (i)
         rd *= 256;
   }
   return rd;
}

/** Puts the next block into place in the reconstructed directory.
 * 
 * @param blockbuffer
 * The data buffer to be read to obtain file data.
 *
 * @param[in,out] tsize
 * On input the amount of data that is to be taken from the buffer, on output
 * the amount of data consumed by the file output process. */
void StreamToDirectory::putNextBlock(char *blockbuffer, apr_off_t *tsize)
{
   apr_pool_clear(mp);
   apr_status_t status;
   int count = 0;
   while (count < (*tsize))
   {
      apr_off_t tlen = (*tsize) - count;
      int retval = recreateFile(&(blockbuffer[count]), &tlen);
      if (retval >= 0)
         count += tlen;
      else
         break;
   }
   *tsize = count;
}

/** Recreate the file described in the stream by reading through the stream.
 * 
 * @param blockbuffer
 * Stream data from which to read file information.
 * 
 * @param[in,out] maxlen
 * On input, the maximum length of data that should be consumed from the
 * buffer, while on output this holds the amount of data that was actually
 * consumed.
 * 
 * @return
 * Zero if the current file being written was completed, one if the buffer
 * ran out beforehand and more of the stream needs to be read before the
 * remainder of the file can be reconstructed. Other values less than
 * zero denote an error. */
int StreamToDirectory::recreateFile(char *blockbuffer, apr_off_t *maxlen)
{
   apr_status_t status;
   apr_off_t remlen = *maxlen;
   apr_off_t pnt = 0;
   if (currflag == READING_FILE_NAME_SIZE)
   {
      apr_off_t by = min_v(8-currbytesthrough, remlen);
      memcpy(&(nxtfilenamesze_encoded[currbytesthrough]),
             &(blockbuffer[pnt]),
             by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt;
         return 1;
      } else
      {
         nxtfilenamesze = readLogical64Bit(nxtfilenamesze_encoded);
         if (nxtfilename) free(nxtfilename);
         nxtfilename = (char *)malloc(sizeof(char)*(nxtfilenamesze+1));
         currbytesthrough = 0;
         currflag = READING_FILE_STREAM_SIZE;
      }
   }
   if (currflag == READING_FILE_STREAM_SIZE)
   {
      apr_off_t by = min_v(8-currbytesthrough, remlen);
      memcpy(&(nxtfilestreamsze_encoded[currbytesthrough]),
             &(blockbuffer[pnt]),
             by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt;
         return 1;
      } else
      {
         nxtfilestreamsze = readLogical64Bit(nxtfilestreamsze_encoded);
         currbytesthrough = 0;
         currflag = READING_NAME;
      }
   }
   if (currflag == READING_NAME)
   {
      apr_off_t by = min_v(nxtfilenamesze-currbytesthrough, remlen);
      memcpy(&(nxtfilename[currbytesthrough]),
             &(blockbuffer[pnt]),
             by);
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt;
         return 1;
      } else
      {
         nxtfilename[nxtfilenamesze] = '\0';
         currbytesthrough = 0;
         currflag = READING_STREAM;
      }
   }
   if (currflag == READING_STREAM)
   {
      apr_off_t by = min_v(nxtfilestreamsze-currbytesthrough, remlen);
      apr_file_t *newFP;
      apr_status_t status;
      char *tfilename = StringUtils::varprintf("%s/%s", directory_name, nxtfilename);
      char *qfilename = strdup(tfilename);
      int last = -1;
      for (int i = 0; i < strlen(qfilename); i++)
         last = (qfilename[i] == '/') ? i : last;
      qfilename[last] = '\0';
      status = APRFile::dirMakeRecursiveWrapper(qfilename,
         APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE, mp);
      CheckAPRError(status);
      errprint("   D %s", qfilename);
      free(qfilename);
      status = APRFile::openWrapper(&newFP, tfilename,
         APR_FOPEN_CREATE|APR_FOPEN_BINARY|APR_FOPEN_WRITE|APR_FOPEN_BUFFERED,
         APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE, mp);
      errprint("   F %s", tfilename);
      CheckAPRError(status);
      apr_off_t cbt = currbytesthrough;
      status = apr_file_seek(newFP, APR_SET, &cbt);
      CheckAPRError(status);
      apr_size_t byteswritten = 0;
      status = apr_file_write_full(newFP, &(blockbuffer[pnt]), by, &byteswritten);
      CheckAPRError(status);
      status = apr_file_close(newFP);
      CheckAPRError(status);
      free(tfilename);
      by = byteswritten;
      pnt += by;
      remlen -= by;
      if (!remlen)
      {
         currbytesthrough += by;
         *maxlen = pnt;
         return 1;
      } else
      {
         currbytesthrough = 0;
         currflag = READING_FILE_NAME_SIZE;
         *maxlen = pnt;
         return 0;
      }
   }
   *maxlen = pnt;
   return -1;
}

int StreamToDirectory::PutNextBlockDecrypted(const char *cname, KeyPair *kp, const char *realbuffer, int *realsize, int *cesize, char **hash)
{
   if ((directory_name == NULL) || (kp == NULL))
   {
      if (!directory_name) errprint("no directory specified");
      if (!kp) errprint("cannot decrypt with no key");
      *cesize = 0;
      *realsize = 0;
      return -1;
   }
   std::vector<char> *dec_block_vec = Broker4::SimpleCryptDataChunk(cname, NULL, kp, NULL, realbuffer, *cesize, 1, 1, mp, NULL);
   if ((dec_block_vec == NULL) || (dec_block_vec->size() == 0)) { errprint("no data decrypted"); *realsize = 0; return -1; }
   apr_off_t decsize = dec_block_vec->size();
   if (hash)
      *hash = BaseConvert::GetSHA1Hash(&(dec_block_vec->at(0)), decsize);
   if (realsize) *realsize = decsize;
   putNextBlock(&(dec_block_vec->at(0)), &decsize);
   return (decsize == (*realsize));
}

int StreamToDirectory::PutNextBlockDecrypted(const char *decwith, KeyDB *kdb, const char *realbuffer, int *realsize, int *cesize, char **hash)
{
   KeyPair *kp = kdb->GetPrivateKeyByName(decwith);
   int retval = PutNextBlockDecrypted(decwith, kp, realbuffer, realsize, cesize, hash);
   delete kp;
   return retval;
}
